package be.donovan.gui.helper.components;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.table.DefaultTableModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.util.ReflectionUtils;

import be.donovan.gui.helper.ServiceLocator;

public class MVCTableModel<T> extends DefaultTableModel {
	private Log logger = LogFactory.getLog(MVCTableModel.class);
	private ResourceBundleMessageSource messageSource = ServiceLocator
			.getService(ResourceBundleMessageSource.class);
	protected boolean firstRowAdded = false;

	protected TreeMap<Integer, T> dataMap = new TreeMap<Integer, T>();

	protected TreeMap<Integer, Integer> rowMapping = new TreeMap<Integer, Integer>();

	protected Vector<String> propertyGetterNames = null;

	protected Vector<Class> columnClasses = new Vector<Class>();

	protected Vector<String> heading = null;

	private Collection<T> data = null;

	public MVCTableModel(Collection<T> data,
			Vector<String> propertyGetterNames, Vector<String> displayNames) {
		this.propertyGetterNames = propertyGetterNames;
		this.heading = displayNames;
		super.setColumnIdentifiers(heading);
		init(data);
	}

	private void init(Collection<T> data) {
		this.data = data;
		rowMapping.clear();
		setMapFromCollection(data);
		super.dataVector = toDataVector();
		fireTableDataChanged();
	}

	private void setMapFromCollection(Collection<T> data) {
		dataMap.clear();
		for (T dto : data) {
			dataMap.put(System.identityHashCode(dto), dto);
		}
	}

	private Vector<Object> toRowVector(T dto) {
		Vector<Object> rowVector = new Vector<Object>();
		for (String propGetter : propertyGetterNames) {
			try {
				logger.debug("try to find method : "+propGetter+" in "+ dto.getClass());
				Method m = ReflectionUtils.findMethod(dto.getClass(),
						propGetter, new Class[] {});
				Object invoke = m.invoke(dto);
				rowVector.add(invoke);
				if (!firstRowAdded) {
					if (invoke instanceof Collection) {
						columnClasses.add(String.class);
					} else {
						columnClasses.add(m.getReturnType());
					}
				}
			} catch (Exception e) {
				Logger.getLogger(this.getClass()).error(e.getMessage(), e);
			}
		}
		firstRowAdded = true;
		return rowVector;
	}

	private Vector<Vector<Object>> toDataVector() {
		Vector<Vector<Object>> dataVector = new Vector<Vector<Object>>();
		int index = 0;
		for (T dto : data) {
			dataVector.add(index, toRowVector(dto));
			rowMapping.put(index, System.identityHashCode(dto));
			index++;
		}
		return dataVector;
	}

	public void add(T dto) {
		addRow(toRowVector(dto));
		int id = System.identityHashCode(dto);
		dataMap.put(id, dto);
		rowMapping.put(getRowCount() - 1, id);
	}

	public void update(T dto) {
		int index = indexOf(dto);
		if (index != -1) {
			removeRow(index);
			insertRow(index, toRowVector(dto));
			int id = System.identityHashCode(dto);
			dataMap.put(id, dto);
		}
	}

	public void remove(T dto) {
		int index = indexOf(dto);
		if (index != -1) {
			removeRow(index);
			int id = System.identityHashCode(dto);
			dataMap.remove(id);
			rowMapping.remove(index);
			Set<Integer> indices = new TreeSet<Integer>(rowMapping.keySet());
			for (Integer idx : indices) {
				if (idx > 0) {
					rowMapping.put(idx - 1, rowMapping.get(idx));
				}
			}
		}
	}

	public T findByRow(int rowIndex) {
		return dataMap.get(rowMapping.get(rowIndex));
	}

	@Override
	public boolean isCellEditable(int row, int column) {
		return false;
	}

	@Override
	public Class<?> getColumnClass(int columnIndex) {
		if (columnClasses == null || columnIndex >= columnClasses.size()) {
			return String.class;
		}
		return (columnClasses.get(columnIndex) == null ? String.class
				: columnClasses.get(columnIndex));
	}

	public Collection<T> getObjects() {
		return dataMap.values();
	}

	private int indexOf(T object) {
		for (Integer index : rowMapping.keySet()) {
			if (rowMapping.get(index).equals(System.identityHashCode(object))) {
				return index;
			}
		}
		return -1;
	}

	public void refresh(Collection<T> data) {
		init(data);
	}

	public String getColumnName(int column) {
		String columnName = super.getColumnName(column);
		return messageSource.getMessage(columnName, null, columnName, null);
	}

	public Collection<T> getData() {
		return data;
	}
}
